/*
 * The MIT License
 *
 * Copyright 2014 David Rodenas Pico.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package edu.uoc.sd.fr14.model;

import java.io.Serializable;

/**
 * This class is a seat operation.
 * <p>
 * 
 * This class handles what is going on with the seat operation
 * and it can be used to follow all steps in the distributed seat operation.
 * <p>
 * 
 * You should not need to use any method of this function, 
 * it should be a black box that you get and you pass to
 * other funcions.
 * <p>
 * 
 * It is generated by {@link Log#annotate(FlightId, CustomerId, ProxyId)}
 * in which it is created and an OpId is assigned,
 * and later is obtained with {@link Log#prepared(OpId, Quorum)}
 * and executed by {@link Log#executeOp(Op, Company)}.
 * It is also recovered inside {@link SubLog} with methods of
 * {@link Log#recover(Nseq)}, and applied to the follower
 * with {@link SubLog#apply(Company)}.
 * <p>
 * 
 * @author drodenas
 */
public final class Op implements Serializable {
	private static final long serialVersionUID = 714614612963812133L;
    protected Op() { logCtx=null;id=null;flightId=null;customerId=null;proxyId=null; }
	
	private static final Logger log = Logger.get("ModelLog");
    private String logCtx; // for debug
    private final OpId id;
    private final FlightId flightId;
    private final CustomerId customerId;
    private final ProxyId proxyId;
    private int preparedCount;

    Op(ServerId server,OpId id, FlightId flightId, CustomerId customerId, ProxyId proxyId) {
        assert id != null;
        assert flightId != null;
        assert customerId != null;
        
        this.logCtx = server+"|"+id;
        this.id = id;
        this.flightId = flightId;
        this.customerId = customerId;
        this.proxyId = proxyId;
    }

    @Override
    public String toString() {
        return id + ":" + flightId + ":" + customerId + ":" + preparedCount;
    }

    OpId getId() {
        return id;
    }

    /**
     * This method is public just for testing and debugging.
     * 
     * @return the flight of this operation
     */
    public FlightId getFlight() {
        return flightId;
    }

    /**
     * This method is public just for testing and debugging.
     * 
     * @return the customer of this operation
     */
    public CustomerId getCustomer() {
        return customerId;
    }

    /**
     * Use this method to know who issued this operation.
     * <p>
     * 
     * You should use this method when a seat operation
     * is completed successfully and you want to know
     * if there is any server waiting for the answer
     * or you have to answer to the customer.
     * You should basically send the confirmed message
     * to the proxyId that tells this id. 
     * 
     * @return who is waiting an answer from this operation
     * @see Log#annotate(FlightId, CustomerId, ProxyId)
     */
    public ProxyId getProxy() {
        return proxyId;
    }
    
    
    
    /**
     * 
     * @param company
     * @return true if something is changed
     */
    boolean execute(Company company) {
        Flight flight = company.getFlight(flightId);
        int freeSeats = flight.query();
        boolean confirmed = flight.confirm(customerId);
        log.debug(logCtx, "execute(company) freeSeats:"+freeSeats+" confirmed:"+confirmed+" force?"+(freeSeats > 0 && !confirmed));
        if (freeSeats > 0 && !confirmed) {
            forceExecute(company);
            return true;
        } else {
            return false;
        }
    }
    
    void forceExecute(Company company) {
        log.debug(logCtx, "forceExecute(company)");
        Flight flight = company.getFlight(flightId);
        flight.seat(customerId);
    }

    boolean hasQuorumOverflow(Quorum quorum) {
        return quorum.isOverflow(preparedCount);
    }

    boolean prepared(Quorum quorum) {
        log.debug(logCtx, "prepared("+quorum+") count: "+preparedCount+"->"+(preparedCount+1)+" result:"+quorum.satisfies(preparedCount+1));
        preparedCount = preparedCount + 1;
        boolean prepared = quorum.satisfies(preparedCount);
        return prepared;
    }
    
    
    
}
